# MariposaDB Configure Library

# Copyright 2008 Matt Mankins <mankins @ gmail . com>
#   
#    Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#   
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

# Manage the reading of command line, configuration files, ENV variables, etc.

package MariposaDB::Config;

use strict;
use FindBin qw($Bin);
use lib "$Bin/../lib";

use Log::Log4perl qw(:easy);
use Data::Dumper;
use Getopt::Long;
use Config::General;

sub new
{
    my $class = shift;
    my ($self)  = @_;

    $self = $self || {};
    bless ($self, $class);

    DEBUG "config started";

    unless ($self->{'search_path'})
    {
	# reads in MARIPOSADB_SEARCH_PATH as well, adding it to this list
	$self->set_path([ "./","etc/","../etc/","/etc/","/usr/local/etc/"]);
    }

    # save the ARGV for others
    $self->{'argv'} = [ @ARGV ];

    DEBUG "ARGV = ".join(" ",@ARGV);

    # read in the config
    $self->get_config(); #available at $self->{'config'}, status at $self->{'config_status'}

    return $self;
}

sub get_config
{
    my $self = shift;

    # first check to see if we've already loaded the config, and if so, return that straightaway
    if ($self->{'config'})
    {
	return $self->{'config'};
    }

    # otherwise, assume it's starting up an app and read the command line

    my $help = 0;
    
    my (%disk_config,%config);

    # get any command-line options
    Getopt::Long::GetOptions(
	'help|?' => \$help, 
	'config=s' => \$config{'config'},
	'mariposadb_log=s' => \$config{'mariposadb_log'},
	'mariposadb_data_dir=s' => \$config{'mariposadb_data_dir'},
	'memcached_start=s' => \$config{'memcached_start'}, 
	'mariposadb_commitlog_dir=s' => \$config{'mariposadb_commitlog_dir'}, 
	'membership=s' => \$config{'membership'}, 
	'seed_host=s' => \$config{'seed_host'},
	'storage_engine=s' => \$config{'storage_engine'}, 
	'storage_engine_start=s' => \$config{'storage_engine_start'}, 
	'membership_daemon=s' => \$config{'membership_daemon'}, 
	'mariposadb_log=s' => \$config{'mariposadb_log'}, 
	'membership_daemon_start=s' => \$config{'membership_daemon_start'}, 
	'memcached=s' => \$config{'memcached'}, 
	'auto_join=s' => \$config{'auto_join'}, 
	'nodes=s' => \$config{'nodes'}, 
	'node=s' => \$config{'node'}, 
	'db_version=s' => \$config{'db_version'}, 
	'node_weight=s' => \$config{'node_weight'}, 
	'id_app=s' => \$config{'id_app'}, 
	'id_physical=s' => \$config{'id_physical'}, 
	'loc_rack_id=s' => \$config{'loc_rack_id'}, 
	'loc_computer_id=s' => \$config{'loc_computer_id'}, 
	'loc_datacenter_id=s' => \$config{'loc_datacenter_id'}, 
	'loc_city_id=s' => \$config{'loc_city_id'}, 
	'loc_state_id=s' => \$config{'loc_state_id'}, 
	'loc_country_id=s' => \$config{'loc_country_id'}, 
	'loc_continent_id=s' => \$config{'loc_continent_id'}, 
	'loc_node_location=s' => \$config{'loc_node_location'}, 
	'loc_=s' => \$config{'loc_'}, 

	'storage=s' => \$config{'storage'}
	);

  SHORT_CIRCUIT_ON_ERROR:
    {
	if ($help)
	{
	    # they asked for help
	    $self->{'config_status'} = "help";
	    return;
	}
	
	if ( ($config{'config'}) && (!(-e $config{'config'})) )
	{
	    $self->{'config_status'} = "error";
	    $self->{'config_error'} = "Unable to find config file, $config{'config'}.\n";
	    return; 
	}
    }

    # if they haven't passed a command line config file, we try to find one.
    my $found_config = "./mariposadb.conf"; #default to ./
    
  LOOK_FOR_CONFIG:
    {
	last LOOK_FOR_CONFIG if $config{'config'};
	       
	foreach my $possible_location (@{ $self->{'search_path'} })
	{
	    if (-e $possible_location."mariposadb.conf")
	    {
		$found_config = $possible_location."mariposadb.conf";
		last LOOK_FOR_CONFIG;
	    }
	}
    }

    my $config_file = $config{'config'} || $found_config;
    
    unless (-e $config_file)
    {
	$self->{'config_status'} = "error";
	$self->{'config_error'} = "Unable to find $config_file [$0], unable to start.";
	return; 
    }

    %disk_config = Config::General::ParseConfig($config_file);
    Log::Log4perl::init_and_watch($config_file); #reloads for logger changes only
    
    DEBUG "Config file, $config{'config'}, loaded:";
    DEBUG Dumper \%disk_config;
    
    # now merge the disk config with any command line overrides
    foreach my $variable (keys %disk_config)
    {
	if (defined $config{$variable})
	{
	    DEBUG "Using command line value for $variable";
	}
	else
	{
	    $config{$variable} = $disk_config{$variable};
	    DEBUG "variable $variable set from disk to ".$disk_config{$variable};
	}
    }
    
    DEBUG "After merging with command line, now:";
    DEBUG Dumper \%config;

  IDENTITY:
    {
	# now we run the ID app to get our "physical" ID.
	
	next IDENTITY if ($config{'id_physical'});

	unless (-x $config{'id_app'})
	{
	    foreach my $possible_location ("./","bin/","../bin/","/bin/","/usr/bin/","/sbin/","/usr/local/sbin/","/usr/local/bin/")
	    {
		if (-x $possible_location.$config{'id_app'})
		{
		    my $path = $possible_location.$config{'id_app'};
		    $config{'id_physical'} = `$path`; # allow to be set on command line via id_physical, but not encouraged. 
		    chomp $config{'id_physical'};
		    last IDENTITY;
		}
	    }
	}

	FATAL "Unable to set Identity.";
	die "Unable to set Identity.";
    }

    # defaults
    $config{"node_weight"} ||= 10;
    $config{"memcached"} ||= "127.0.0.1:11211";
    $config{"seed_host"} ||= "127.0.0.1:30009";
    
    # save for subsequent requests
    $self->{'config'} = \%config;

    # and done
    $self->{'config_status'} = "ok";
    $self->{'config_error'} = "";

    return $self->{'config'};
}

sub get
{
    my $self = shift;

    my ($variable) = @_;

    if ($variable && (exists $self->{'config'}->{$variable}))
    {
	DEBUG "$variable = ".$self->{'config'}->{$variable};
	return $self->{'config'}->{$variable};
    }
    else
    {
	DEBUG "$variable not defined";
	return undef;
    }
}

sub set_path
{
    # set the search path for config files
    my $self = shift;
    my ($search_path_ref) = @_;

    my @search_path;
    @search_path = @{ $search_path_ref } if $search_path_ref;
    
    if ($ENV{"MARIPOSADB_SEARCH_PATH"})
    {
	# each time this gets called, we could add more of the same to the path. FIXME
	push (@search_path, split(/,/,$ENV{"MARIPOSADB_SEARCH_PATH"} ));
    }

    $self->{'search_path'} = \@search_path;
    DEBUG Dumper $self->{'search_path'};

    return $self->{'search_path'};
}

sub get_node_id
{
    # what is the ID of this particular node?
    my $self = shift;

    # format: PHYSICAL UUID + ":" + NODE (INSTANCE) NUMBER

    my $node = $self->get("node") || "0";

    my $node_id = $self->get("id_physical").":$node";
    return $node_id;
}

1;
